import _extends from "@babel/runtime/helpers/esm/extends";
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import _typeof from "@babel/runtime/helpers/esm/typeof";
import * as React from 'react';
import { updateCSS, removeCSS } from "rc-util/es/Dom/dynamicCSS";
import canUseDom from "rc-util/es/Dom/canUseDom";
import hash from '@emotion/hash'; // @ts-ignore

import unitless from '@emotion/unitless';
import { compile, serialize, stringify } from 'stylis';
import useGlobalCache from "./useGlobalCache";
import StyleContext, { ATTR_MARK, ATTR_TOKEN, ATTR_DEV_CACHE_PATH, CSS_IN_JS_INSTANCE, CSS_IN_JS_INSTANCE_ID } from "../StyleContext";
import { styleValidate, supportLayer } from "../util";
var isClientSide = canUseDom();
var SKIP_CHECK = '_skip_check_';
// ============================================================================
// ==                                 Parser                                 ==
// ============================================================================
// Preprocessor style content to browser support one
export function normalizeStyle(styleStr) {
  var serialized = serialize(compile(styleStr), stringify);
  return serialized.replace(/\{%%%\:[^;];}/g, ';');
}

function isCompoundCSSProperty(value) {
  return _typeof(value) === 'object' && value && SKIP_CHECK in value;
}

export var animationStatistics = {}; // 注入 hash 值

function injectSelectorHash(key, hashId, hashPriority) {
  if (!hashId) {
    return key;
  }

  var hashClassName = ".".concat(hashId);
  var hashSelector = hashPriority === 'low' ? ":where(".concat(hashClassName, ")") : hashClassName; // 注入 hashId

  var keys = key.split(',').map(function (k) {
    var _firstPath$match;

    var fullPath = k.trim().split(/\s+/); // 如果 Selector 第一个是 HTML Element，那我们就插到它的后面。反之，就插到最前面。

    var firstPath = fullPath[0] || '';
    var htmlElement = ((_firstPath$match = firstPath.match(/^\w+/)) === null || _firstPath$match === void 0 ? void 0 : _firstPath$match[0]) || '';
    firstPath = "".concat(htmlElement).concat(hashSelector).concat(firstPath.slice(htmlElement.length));
    return [firstPath].concat(_toConsumableArray(fullPath.slice(1))).join(' ');
  });
  return keys.join(',');
}

// Global effect style will mount once and not removed
// The effect will not save in SSR cache (e.g. keyframes)
var globalEffectStyleKeys = new Set();
/**
 * @private Test only. Clear the global effect style keys.
 */

export var _cf = process.env.NODE_ENV !== 'production' ? function () {
  return globalEffectStyleKeys.clear();
} : undefined; // Parse CSSObject to style content

export var parseStyle = function parseStyle(interpolation) {
  var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

  var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
    root: true
  },
      root = _ref.root,
      injectHash = _ref.injectHash;

  var hashId = config.hashId,
      layer = config.layer,
      path = config.path,
      hashPriority = config.hashPriority,
      _config$transformers = config.transformers,
      transformers = _config$transformers === void 0 ? [] : _config$transformers;
  var styleStr = '';
  var effectStyle = {};

  function parseKeyframes(keyframes) {
    var animationName = keyframes.getName(hashId);

    if (!effectStyle[animationName]) {
      var _parseStyle = parseStyle(keyframes.style, config, {
        root: false
      }),
          _parseStyle2 = _slicedToArray(_parseStyle, 1),
          _parsedStr = _parseStyle2[0];

      effectStyle[animationName] = "@keyframes ".concat(keyframes.getName(hashId)).concat(_parsedStr);
    }
  }

  function flattenList(list) {
    var fullList = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
    list.forEach(function (item) {
      if (Array.isArray(item)) {
        flattenList(item, fullList);
      } else if (item) {
        fullList.push(item);
      }
    });
    return fullList;
  }

  var flattenStyleList = flattenList(Array.isArray(interpolation) ? interpolation : [interpolation]);
  flattenStyleList.forEach(function (originStyle) {
    // Only root level can use raw string
    var style = typeof originStyle === 'string' && !root ? {} : originStyle;

    if (typeof style === 'string') {
      styleStr += "".concat(style, "\n");
    } else if (style._keyframe) {
      // Keyframe
      parseKeyframes(style);
    } else {
      var mergedStyle = transformers.reduce(function (prev, trans) {
        var _trans$visit;

        return (trans === null || trans === void 0 ? void 0 : (_trans$visit = trans.visit) === null || _trans$visit === void 0 ? void 0 : _trans$visit.call(trans, prev)) || prev;
      }, style); // Normal CSSObject

      Object.keys(mergedStyle).forEach(function (key) {
        var value = mergedStyle[key];

        if (_typeof(value) === 'object' && value && (key !== 'animationName' || !value._keyframe) && !isCompoundCSSProperty(value)) {
          var subInjectHash = false; // 当成嵌套对象来处理

          var mergedKey = key.trim(); // Whether treat child as root. In most case it is false.

          var nextRoot = false; // 拆分多个选择器

          if ((root || injectHash) && hashId) {
            if (mergedKey.startsWith('@')) {
              // 略过媒体查询，交给子节点继续插入 hashId
              subInjectHash = true;
            } else {
              // 注入 hashId
              mergedKey = injectSelectorHash(key, hashId, hashPriority);
            }
          } else if (root && !hashId && (mergedKey === '&' || mergedKey === '')) {
            // In case of `{ '&': { a: { color: 'red' } } }` or `{ '': { a: { color: 'red' } } }` without hashId,
            // we will get `&{a:{color:red;}}` or `{a:{color:red;}}` string for stylis to compile.
            // But it does not conform to stylis syntax,
            // and finally we will get `{color:red;}` as css, which is wrong.
            // So we need to remove key in root, and treat child `{ a: { color: 'red' } }` as root.
            mergedKey = '';
            nextRoot = true;
          }

          var _parseStyle3 = parseStyle(value, _objectSpread(_objectSpread({}, config), {}, {
            path: "".concat(path, " -> ").concat(mergedKey)
          }), {
            root: nextRoot,
            injectHash: subInjectHash
          }),
              _parseStyle4 = _slicedToArray(_parseStyle3, 2),
              _parsedStr2 = _parseStyle4[0],
              childEffectStyle = _parseStyle4[1];

          effectStyle = _objectSpread(_objectSpread({}, effectStyle), childEffectStyle);
          styleStr += "".concat(mergedKey).concat(_parsedStr2);
        } else {
          var _value;

          var actualValue = (_value = value === null || value === void 0 ? void 0 : value.value) !== null && _value !== void 0 ? _value : value;

          if (process.env.NODE_ENV !== 'production' && (_typeof(value) !== 'object' || !(value !== null && value !== void 0 && value[SKIP_CHECK]))) {
            styleValidate(key, actualValue, {
              path: path,
              hashId: hashId
            });
          } // 如果是样式则直接插入


          var styleName = key.replace(/[A-Z]/g, function (match) {
            return "-".concat(match.toLowerCase());
          }); // Auto suffix with px

          var formatValue = actualValue;

          if (!unitless[key] && typeof formatValue === 'number' && formatValue !== 0) {
            formatValue = "".concat(formatValue, "px");
          } // handle animationName & Keyframe value


          if (key === 'animationName' && value !== null && value !== void 0 && value._keyframe) {
            parseKeyframes(value);
            formatValue = value.getName(hashId);
          }

          styleStr += "".concat(styleName, ":").concat(formatValue, ";");
        }
      });
    }
  });

  if (!root) {
    styleStr = "{".concat(styleStr, "}");
  } else if (layer && supportLayer()) {
    var layerCells = layer.split(',');
    var layerName = layerCells[layerCells.length - 1].trim();
    styleStr = "@layer ".concat(layerName, " {").concat(styleStr, "}"); // Order of layer if needed

    if (layerCells.length > 1) {
      // zombieJ: stylis do not support layer order, so we need to handle it manually.
      styleStr = "@layer ".concat(layer, "{%%%:%}").concat(styleStr);
    }
  }

  return [styleStr, effectStyle];
}; // ============================================================================
// ==                                Register                                ==
// ============================================================================

function uniqueHash(path, styleStr) {
  return hash("".concat(path.join('%')).concat(styleStr));
}

function Empty() {
  return null;
}
/**
 * Register a style to the global style sheet.
 */


export default function useStyleRegister(info, styleFn) {
  var token = info.token,
      path = info.path,
      hashId = info.hashId,
      layer = info.layer;

  var _React$useContext = React.useContext(StyleContext),
      autoClear = _React$useContext.autoClear,
      mock = _React$useContext.mock,
      defaultCache = _React$useContext.defaultCache,
      hashPriority = _React$useContext.hashPriority,
      container = _React$useContext.container,
      ssrInline = _React$useContext.ssrInline,
      transformers = _React$useContext.transformers;

  var tokenKey = token._tokenKey;
  var fullPath = [tokenKey].concat(_toConsumableArray(path)); // Check if need insert style

  var isMergedClientSide = isClientSide;

  if (process.env.NODE_ENV !== 'production' && mock !== undefined) {
    isMergedClientSide = mock === 'client';
  }

  var _useGlobalCache = useGlobalCache('style', fullPath, // Create cache if needed
  function () {
    var styleObj = styleFn();

    var _parseStyle5 = parseStyle(styleObj, {
      hashId: hashId,
      hashPriority: hashPriority,
      layer: layer,
      path: path.join('-'),
      transformers: transformers
    }),
        _parseStyle6 = _slicedToArray(_parseStyle5, 2),
        parsedStyle = _parseStyle6[0],
        effectStyle = _parseStyle6[1];

    var styleStr = normalizeStyle(parsedStyle);
    var styleId = uniqueHash(fullPath, styleStr); // Clear animation statistics

    animationStatistics = {};

    if (isMergedClientSide) {
      var style = updateCSS(styleStr, styleId, {
        mark: ATTR_MARK,
        prepend: 'queue',
        attachTo: container
      });
      style[CSS_IN_JS_INSTANCE] = CSS_IN_JS_INSTANCE_ID; // Used for `useCacheToken` to remove on batch when token removed

      style.setAttribute(ATTR_TOKEN, tokenKey); // Dev usage to find which cache path made this easily

      if (process.env.NODE_ENV !== 'production') {
        style.setAttribute(ATTR_DEV_CACHE_PATH, fullPath.join('|'));
      } // Inject client side effect style


      Object.keys(effectStyle).forEach(function (effectKey) {
        if (!globalEffectStyleKeys.has(effectKey)) {
          globalEffectStyleKeys.add(effectKey); // Inject

          updateCSS(normalizeStyle(effectStyle[effectKey]), "_effect-".concat(effectKey), {
            mark: ATTR_MARK,
            prepend: 'queue',
            attachTo: container
          });
        }
      });
    }

    return [styleStr, tokenKey, styleId];
  }, // Remove cache if no need
  function (_ref2, fromHMR) {
    var _ref3 = _slicedToArray(_ref2, 3),
        styleId = _ref3[2];

    if ((fromHMR || autoClear) && isClientSide) {
      removeCSS(styleId, {
        mark: ATTR_MARK
      });
    }
  }),
      _useGlobalCache2 = _slicedToArray(_useGlobalCache, 3),
      cachedStyleStr = _useGlobalCache2[0],
      cachedTokenKey = _useGlobalCache2[1],
      cachedStyleId = _useGlobalCache2[2];

  return function (node) {
    var styleNode;

    if (!ssrInline || isMergedClientSide || !defaultCache) {
      styleNode = /*#__PURE__*/React.createElement(Empty, null);
    } else {
      var _ref4;

      styleNode = /*#__PURE__*/React.createElement("style", _extends({}, (_ref4 = {}, _defineProperty(_ref4, ATTR_TOKEN, cachedTokenKey), _defineProperty(_ref4, ATTR_MARK, cachedStyleId), _ref4), {
        dangerouslySetInnerHTML: {
          __html: cachedStyleStr
        }
      }));
    }

    return /*#__PURE__*/React.createElement(React.Fragment, null, styleNode, node);
  };
} // ============================================================================
// ==                                  SSR                                   ==
// ============================================================================

export function extractStyle(cache) {
  // prefix with `style` is used for `useStyleRegister` to cache style context
  var styleKeys = Array.from(cache.cache.keys()).filter(function (key) {
    return key.startsWith('style%');
  }); // const tokenStyles: Record<string, string[]> = {};

  var styleText = '';
  styleKeys.forEach(function (key) {
    var _ = _slicedToArray(cache.cache.get(key)[1], 3),
        styleStr = _[0],
        tokenKey = _[1],
        styleId = _[2];

    styleText += "<style ".concat(ATTR_TOKEN, "=\"").concat(tokenKey, "\" ").concat(ATTR_MARK, "=\"").concat(styleId, "\">").concat(styleStr, "</style>");
  });
  return styleText;
}